using System;
using System.Xml;
using System.IO;
using System.Collections.Generic;
using Subsync.Core.Subversion;

namespace Subsync.Core.Sync
{
	/// <summary>
	/// Author: Steven Goh Jian Wen, except the methods with Author: Hoang Duc
	/// The config file exists in appData for Windows OS.
	/// </summary>
	public class Config
	{
		string filename = "config.xml";
		string file_path;
		string img_dir;
		string repo_dir;
		
		XmlDocument config = new XmlDocument();

		public Config ()
		{
			//Determine user's OS
			OperatingSystem os = Environment.OSVersion;
			PlatformID pid = os.Platform;
			string dir;
			switch (pid) {
			case PlatformID.Win32Windows:
            case PlatformID.Win32NT:
				dir = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
				dir = System.IO.Path.Combine (dir, "Subsync");
				if (!Directory.Exists (dir))
					Directory.CreateDirectory (dir);
				file_path = Path.Combine (dir, this.filename);
				repo_dir = Path.Combine (dir, "_repo");
				break;
			default:
				file_path = Path.Combine (Directory.GetCurrentDirectory (), this.filename);
				repo_dir = Path.Combine (Directory.GetCurrentDirectory (), "_repo");
				break;
			}

            img_dir = Path.Combine(Directory.GetCurrentDirectory(), "_images");

			if (!File.Exists (this.file_path))
				InitConfig ();
            try
            {
                config.Load(file_path);
            }
            catch (XmlException)
            {
                Console.WriteLine("Invalid XML file");
            }
		}
		
		public void DeleteSync (Sync sync_obj)
		{
			XmlNode delete_me = null;
			XmlNodeList sync_db = config.GetElementsByTagName ("Sync");
			
			foreach (XmlNode s in sync_db) {
				foreach (XmlNode attr in s.ChildNodes) {
					if ((attr.Name == "Name") && (attr.ChildNodes[0].Value == sync_obj.name))
						delete_me = s;
				}
			}
			
			XmlNode syncdb = config.GetElementsByTagName ("SyncDB")[0];
			if (delete_me != null) syncdb.RemoveChild (delete_me);
			
			this.Save ();
		}
		
		public void SaveSync (Sync sync_obj)
		{
			DeleteSync (sync_obj);
		
			//build the elements
			XmlElement new_sync = config.CreateElement ("Sync");
			XmlElement sync_name = config.CreateElement ("Name");
			XmlElement sync_repo_url = config.CreateElement ("RepoURL");
			XmlElement sync_workingcopy = config.CreateElement ("WorkingCopyPath");
			XmlElement sync_autoconflicts = config.CreateElement ("AutoManageConflicts");
			XmlElement sync_hosted = config.CreateElement ("Hosted");
			XmlElement sync_desc = config.CreateElement ("Description");
			XmlElement sync_icon = config.CreateElement ("IconPath");
			XmlElement sync_lastsync = config.CreateElement ("LastSync");
			
			XmlText name_txt = config.CreateTextNode (sync_obj.name);
			XmlText repourl_text = config.CreateTextNode (sync_obj.repo_url);
			XmlText workingcopy_text = config.CreateTextNode (sync_obj.working_copy_path);
			XmlText autoconflicts_text = config.CreateTextNode (Tools.BoolToYesNo (sync_obj.auto_conflict_management));
			XmlText hosted_text = config.CreateTextNode (Tools.BoolToYesNo (sync_obj.hosted));
			XmlText desc_txt;
			if (sync_obj.description.Trim () != "")
				desc_txt = config.CreateTextNode (sync_obj.description);
			else
				desc_txt = config.CreateTextNode ("No Description");
			XmlText icon_txt = config.CreateTextNode (sync_obj.icon);
			XmlText lastsync_txt = config.CreateTextNode ("Never");
			
			sync_name.AppendChild (name_txt);
			sync_repo_url.AppendChild (repourl_text);
			sync_workingcopy.AppendChild (workingcopy_text);
			sync_autoconflicts.AppendChild (autoconflicts_text);
			sync_hosted.AppendChild (hosted_text);
			sync_desc.AppendChild (desc_txt);
			sync_icon.AppendChild (icon_txt);
			sync_lastsync.AppendChild (lastsync_txt);
			
			new_sync.AppendChild (sync_name);
			new_sync.AppendChild (sync_repo_url);
			new_sync.AppendChild (sync_workingcopy);
			new_sync.AppendChild (sync_autoconflicts);
			new_sync.AppendChild (sync_hosted);
			new_sync.AppendChild (sync_desc);
			new_sync.AppendChild (sync_icon);
			new_sync.AppendChild (sync_lastsync);
			
			XmlNode syncdb = config.GetElementsByTagName ("SyncDB")[0];
			syncdb.AppendChild (new_sync);			
			
			//save it
			this.Save ();
		}
		
		public Sync LoadSync (string sync_name)
		{
			Sync ret = new Sync ();
			XmlNode found = null;
			
			XmlNodeList sync_db = config.GetElementsByTagName ("Sync");
			
			foreach (XmlNode s in sync_db)
				foreach (XmlNode attr in s.ChildNodes)
					if ((attr.Name == "Name") && (attr.ChildNodes[0].Value == sync_name))
						found = s;

			if (found == null)
				throw new SyncNotFound ("Unable to find Sync, are you sure you have set it up?");
			
			foreach (XmlNode attr in found) {
				if (attr.Name == "Name")
					ret.name = attr.ChildNodes[0].Value;
				else if (attr.Name == "RepoURL")
					ret.repo_url = attr.ChildNodes[0].Value;
				else if (attr.Name == "WorkingCopyPath")
					ret.working_copy_path = attr.ChildNodes[0].Value;
				else if (attr.Name == "AutoManageConflicts")
					ret.auto_conflict_management = Tools.StringToBool (attr.ChildNodes[0].Value);
				else if (attr.Name == "Hosted")
					ret.hosted = Tools.StringToBool (attr.ChildNodes[0].Value);
				else if (attr.Name == "Description")
					ret.description = attr.ChildNodes[0].Value;
				else if (attr.Name == "IconPath") 
					ret.icon = attr.ChildNodes[0].Value;
				else if (attr.Name == "LastSync")
					ret.last_sync = attr.ChildNodes[0].Value;
			}
			
			ret.def_repos_dir = config.GetElementsByTagName ("RepoStoragePath")[0].ChildNodes[0].Value;
			
			return ret;
		}
		
		public string GetProperty (string attr)
		{ 
			return config.GetElementsByTagName (attr)[0].ChildNodes[0].Value;
		}
		
		public void SetProperty (string attr, string value)
		{
			config.GetElementsByTagName (attr)[0].ChildNodes[0].Value = value;
			this.Save ();
		}
		
		public void InitConfig ()
		{
			XmlDeclaration xml_declaration = config.CreateXmlDeclaration ("1.0", "utf-8", null);
			
			// Create the root element
			XmlElement root_node = config.CreateElement ("Config");
			config.InsertBefore (xml_declaration, config.DocumentElement);
			config.AppendChild (root_node);
			
			XmlElement repo_storage_path = config.CreateElement ("RepoStoragePath");
			if (!Directory.Exists (repo_dir))
				Directory.CreateDirectory (repo_dir);
			XmlText rsp_txt = config.CreateTextNode (repo_dir);
			repo_storage_path.AppendChild (rsp_txt);
			
			XmlElement img_storage_path = config.CreateElement ("ImageStoragePath");
			string img_dir = Path.Combine (Directory.GetCurrentDirectory (), "_images");
			if (!Directory.Exists (img_dir))
				Directory.CreateDirectory (img_dir);
			XmlText isp_txt = config.CreateTextNode (img_dir);
			img_storage_path.AppendChild (isp_txt);
			
			//automatically start svnserve
			XmlElement svnserve_opt = config.CreateElement ("ManualSVNServe");
			XmlText opt_txt = config.CreateTextNode ("no");
			svnserve_opt.AppendChild (opt_txt);
			
			//path to svnserve
			XmlElement svnservepath = config.CreateElement ("SVNServePath");
			XmlText svnservepath_txt = config.CreateTextNode (GetSvnServeDefPath ());
			svnservepath.AppendChild (svnservepath_txt);
			
			//minimize to tray opt
			XmlElement minimizetray_opt = config.CreateElement ("MinimizeTray");
			XmlText minimizetray_txt = config.CreateTextNode ("no");
			minimizetray_opt.AppendChild (minimizetray_txt);
			
			XmlElement syncdb = config.CreateElement ("SyncDB");
			
			
			config.DocumentElement.PrependChild (repo_storage_path);
			config.DocumentElement.PrependChild (img_storage_path);
			config.DocumentElement.PrependChild (svnserve_opt);
			config.DocumentElement.PrependChild (syncdb);
			
			this.Save ();
		}
		
		private string GetSvnServeDefPath ()
		{
			return "Select your SVNServe executable";
		}
		
		public List<Sync> GetAllSyncs ()
		{
			List<Sync> ret_all_sync = new List<Sync> ();
			
			XmlNodeList sync_db = config.GetElementsByTagName ("Sync");
			foreach (XmlNode s in sync_db) {
				
				Sync ret = new Sync ();
				foreach (XmlNode attr in s) {
					if (attr.Name == "Name")
						ret.name = attr.ChildNodes[0].Value; 
					else if (attr.Name == "RepoURL")
						ret.repo_url = attr.ChildNodes[0].Value; 
					else if (attr.Name == "WorkingCopyPath")
						ret.working_copy_path = attr.ChildNodes[0].Value; 
					else if (attr.Name == "AutoManageConflicts")
						ret.auto_conflict_management = Tools.StringToBool (attr.ChildNodes[0].Value); 
					else if (attr.Name == "Hosted")
						ret.hosted = Tools.StringToBool (attr.ChildNodes[0].Value);
					else if (attr.Name == "IconPath")
						ret.icon = attr.ChildNodes[0].Value;
					else if (attr.Name == "Description")
						ret.description = attr.ChildNodes[0].Value;
					else if (attr.Name == "LastSync")
						ret.last_sync = attr.ChildNodes[0].Value;
				}
				ret_all_sync.Add (ret);
			}
			return ret_all_sync;
		}
		
		public void SetSyncProperty (Sync s, string attribute, string value)
		{
			XmlNodeList sync_db = config.GetElementsByTagName ("Sync");
			
			XmlNode found = null;
			
			foreach (XmlNode sa in sync_db)
				foreach (XmlNode attr in sa.ChildNodes)
					if ((attr.Name == "Name") && (attr.ChildNodes[0].Value == s.name))
						found = sa;

			if (found == null)
				throw new SyncNotFound ("Unable to find Sync, are you sure you have set it up?");
			
			foreach (XmlNode attr in found) {
				if (attr.Name == attribute) {
					attr.ChildNodes[0].Value = value;
				}
			}
			this.Save();
		}

		/// <summary>
		/// Author: Hoang Duc, refined by Steven
		/// </summary>
		/// <summary>
		/// Check if the config is valid.
		/// </summary>
		/// <returns>
		/// A <see cref="System.Boolean"/>
		/// </returns>
        public bool ValidConfigCheck ()
        {
        	//check for 2.0 new nodes
        	NewNode2Dot0Check ();
			
        	//check appropriate number of elements
        	if ((config.GetElementsByTagName ("Config").Count != 1) ||
				(config.GetElementsByTagName ("SyncDB").Count != 1) ||
				(config.GetElementsByTagName ("ManualSVNServe").Count != 1) ||
				(config.GetElementsByTagName ("ImageStoragePath").Count != 1) ||
				(config.GetElementsByTagName ("SVNServePath").Count != 1) ||
				(config.GetElementsByTagName ("MinimizeTray").Count != 1))
        		return false;
    
			return CheckSyncs ();
        }
		
		/// <summary>
		/// Check if sync info within SyncDB is accurate
		/// </summary>
		/// <returns>
		/// A <see cref="System.Boolean"/>
		/// </returns>
		bool CheckSyncs ()
		{
			int nodes_per_sync_item = 8;
			List<string> syncs_to_remove = new List<string> ();
			
			//getting the syncs
			XmlNodeList sync_db = config.GetElementsByTagName ("Sync");
			if (sync_db.Count > 0) {
				foreach (XmlNode s in sync_db) {
					if (s.ChildNodes.Count != nodes_per_sync_item) {
						return false;
					}
					foreach (XmlNode attr in s.ChildNodes) {
						if (attr.Name == "WorkingCopyPath") {
							if (!Directory.Exists (attr.ChildNodes[0].Value)) {
								syncs_to_remove.Add (s.FirstChild.ChildNodes[0].Value);
								break;
							}
						} else if (attr.Name == "AutoManageConflicts") {
							string value = attr.ChildNodes[0].Value;
							if (value != "yes" && value != "no") {
								return false;
							}
						} else if (attr.Name == "Hosted") {
							string value = attr.ChildNodes[0].Value;
							if (value != "yes" && value != "no") {
								return false;
							}
						} else if (attr.Name == "IconPath") {
							if (!File.Exists (attr.ChildNodes[0].Value)) {
								return false;
							}
						}
					}
				}
			}
			
			//Removal
			foreach (string sync in syncs_to_remove)
            {
                try
                {
                    Sync s = this.LoadSync (sync);
                    s.Remove ();
                }
                catch (Exception)
                {
                    continue;
                }
            }
			return true;
		}
		
		/// <summary>
		/// Check if the new nodes that are needed in 2.0 are present, if not,
		/// add them.
		/// 
		/// New nodes:
		/// - SVNServePath
		/// - MinimizeTray
		/// </summary>
		void NewNode2Dot0Check ()
		{
			//SVNServePath check
			if (config.GetElementsByTagName ("SVNServePath").Count != 1) {
				DeleteAllElementsByTagName ("SVNServePath");
				
				XmlElement svnservepath = config.CreateElement ("SVNServePath");
				XmlText svnservepath_txt = config.CreateTextNode (GetSvnServeDefPath ());
				svnservepath.AppendChild (svnservepath_txt);
				
				config.DocumentElement.PrependChild (svnservepath);
			}
			
			//Minimize tray check
			if (config.GetElementsByTagName ("MinimizeTray").Count != 1) {
				DeleteAllElementsByTagName ("MinimizeTray");
				
				XmlElement minimizetray_opt = config.CreateElement ("MinimizeTray");
				XmlText minimizetray_txt = config.CreateTextNode ("no");
				minimizetray_opt.AppendChild (minimizetray_txt);
				
				config.DocumentElement.PrependChild (minimizetray_opt);
			}
			this.Save ();
		}
		
		void DeleteAllElementsByTagName (string tagname)
		{
			XmlNodeList elements = config.GetElementsByTagName (tagname);
			foreach (XmlNode e in elements) {
				e.ParentNode.RemoveChild (e);
			}
			this.Save ();
		}
		
		public void Save ()
		{
			config.Save (file_path);
		}
	}
}
